package com.md.config.exception;

import com.md.base.EnumBase;
import com.md.base.exception.ErrorMessage;
import com.md.base.exception.ServerException;
import com.md.util.ObjectUtil;
import com.md.value.constants.CodeConstants;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.InitBinder;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RestControllerAdvice;

import java.util.List;

import static java.lang.String.join;
import static java.util.stream.Collectors.joining;

/**
 * @author 刘骄阳
 * 2023-10-11 14:27
 */
@RestControllerAdvice
@Slf4j
public class GlobalExceptionHandler {
  private static final String SERVER_ERROR_MSG = "网络开小差了，请稍后重试～";

  //（1）全局数据绑定
  //应用到所有@RequestMapping注解方法
  //此处将键值对添加到全局，注解了@RequestMapping的方法都可以获得此键值对
  @ModelAttribute
  public void addUser(final Model model) {
    model.addAttribute("name", "此处将键值对添加到全局，注解了@RequestMapping的方法都可以获得此键值对");
    System.err.println("addUser");
  }

  //（2）全局数据预处理
  //应用到所有@RequestMapping注解方法，在其执行之前初始化数据绑定器
  //用来设置WebDataBinder
  @InitBinder
  public void initBinder(final WebDataBinder binder) {
    System.err.println("initBinder");
  }

  // （3）全局异常处理
  //应用到所有@RequestMapping注解的方法，在其抛出Exception异常时执行
  //定义全局异常处理，value属性可以过滤拦截指定异常，此处拦截所有的Exception
  @ExceptionHandler(value = {Exception.class, ServerException.class})
  public ResponseEntity<ErrorMessage> handleException(final Exception e) {
    if (e instanceof ServerException) {
      // 自定义的异常
      log.warn("Request failed, cause by: {}", e.getMessage(), e);
      ServerException exception = (ServerException) e;
      EnumBase error = exception.getError();
      HttpStatus status = HttpStatus.resolve(error.getCode());
      if (ObjectUtil.isEmpty(status)) {
        status = HttpStatus.BAD_REQUEST;
      }
      return ResponseEntity
        .status(status)
        .body(ErrorMessage.of(error.getCode(), error.getMessage()));
    }
    log.error("Request error, cause by: {}", e.getMessage(), e);
    if (e instanceof MethodArgumentNotValidException) {
      // 参数校验的异常
      MethodArgumentNotValidException validException = (MethodArgumentNotValidException) e;
      BindingResult bindingResult = validException.getBindingResult();
      List<FieldError> allErrors = bindingResult.getFieldErrors();
      String errorMsg = allErrors.stream()
        .map(error -> join(": ", error.getField(), error.getDefaultMessage()))
        .collect(joining(";"));
      return ResponseEntity
        .status(HttpStatus.BAD_REQUEST)
        .body(ErrorMessage.of(HttpStatus.BAD_REQUEST.value(), errorMsg));
    }
    // 其他异常
    return ResponseEntity
      .status(HttpStatus.INTERNAL_SERVER_ERROR)
      .body(ErrorMessage.of(CodeConstants.ERROR_CODE, SERVER_ERROR_MSG));
  }

}
